"""
    jupylet/audio/__init__.py
    
    Copyright (c) 2020, Nir Aides - nir@winpdb.org

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:

    1. Redistributions of source code must retain the above copyright notice, this
       list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright notice,
       this list of conditions and the following disclaimer in the documentation
       and/or other materials provided with the distribution.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
    ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""


import asyncio
import pathlib
import time
import os

from ..utils import callerframe, callerpath


def sonic_py(resource_dir='.'):
    """启动音频应用程序。

    需要一个音频应用程序来运行实时循环。
    
    Args:
        resource_dir (str): 指向资源目录根的路径，用于示例等。

    Returns:
        App: 正在运行的应用程序对象。
    """
    from ..app import App

    red = os.path.join(callerpath(), resource_dir)
    red = pathlib.Path(red).absolute()

    app = App(32, 32, resource_dir=str(red))
    app.run(0)
    
    return app


DEFAULT_AMP = 0.5

MIDDLE_C = 261.63

FPS = 44100


def t2frames(t):
    """以秒为单位将时间数转换为对应每秒44100帧的帧数。
    
    Args:
        t (float): 持续时间（以秒为单位）。

    Returns:
        int: 帧数
    """
    return int(FPS * t)


def frames2t(frames):
    """将每秒44100帧的帧数转换为以秒为单位的时间数。
    
    Args:
        frames (int): 帧数

    Returns:
        float: 持续时间（以秒为单位）。
    """
    return frames  / FPS


def get_time():
    return time.time()
  

_note_value = 4


def set_note_value(v=4):
    """设置表示一个节拍的音符值。
    
    Args:
        v (float): 音符值。
    """
    global _note_value
    _note_value = v


def get_note_value():
    return _note_value


_bpm = 240


def set_bpm(bpm=240):
    """将节奏设置为每分钟给定的节拍数。
    
    Args:
        bpm (float): 每分钟节拍数。
    """
    global _bpm
    _bpm = bpm


def get_bpm():
    return _bpm


dtd = {}
syd = {}


def use(sound, **kwargs):
    """将乐器设置为在后续通话中使用，以便调用 :func:`play` 。
    
    可以提供要在给定工具中修改属性的键/值(key/value)对。如果这样做，
    将首先复制仪器，并将修改应用于新副本。

    Args:
        sound (GatedSound): 要使用的乐器。
        **kwargs: 可以编辑的乐器属性。
    """
    if kwargs:
        sound = sound.copy().set(**kwargs)

    cf = callerframe()
    cn = cf.f_code.co_name

    if cn in ['<module>', 'async-def-wrapper']:
        hh = '<module>'
    else:
        hh = hash(cf) 

    syd[hh] = sound


PLAY_EXTRA_LATENCY = 0.150


def play(note, duration=None, **kwargs):
    """在由  :func:`use` 调用前，设置乐器以复调方式弹奏给定的音符。
    
    可以提供要在给定工具中修改的属性的键/值对。

    Args:
        note (float): 注意以半音为单位演奏，其中60对应中央C音。
        duration (float, optional): 播放音符的持续时间，以整音符为单位。
        **kwargs: 要修改的乐器的属性。
    """

    cf = callerframe()
    cn = cf.f_code.co_name
    
    if cn in ['<module>', 'async-def-wrapper']:
        hh = '<module>'
    else:
        hh = hash(cf) 

    sy = syd[hh]
    
    tt = dtd.get(hh) or get_time()
    tt += PLAY_EXTRA_LATENCY

    return sy.play_poly(note, duration, t=tt, **kwargs)


def sleep(duration=0):
    """休止一下
    
    Example: 
        ::
    
            @app.sonic_live_loop2
            async def boom_pam():
                        
                use(tb303, resonance=8, decay=1/8, cutoff=48, amp=1)
                
                play(C2, 1/8)
                await sleep(1/4)
                
                play(C3, 1/8)
                await sleep(1/4)

    Args:
        duration (float): 整个音符的休止时间

    Returns:
        coroutine: 与 `await` 一起使用的休止协同程序。
    """
    tt = get_time()
    dt = duration

    cf = callerframe()
    cn = cf.f_code.co_name

    if cn in ['<module>', 'async-def-wrapper']:
        hh = '<module>'
    else:
        hh = hash(cf) 

    #sy = syd.get(hh)
    #if sy is not None:
    # 如果sy不是None：
    dt = dt * get_note_value() * 60 / get_bpm()

    t0 = dtd.get(hh)
    if not t0 or t0 + 1 < tt:
        t0 = tt

    t1 = dtd[hh] = max(t0 + dt, tt)

    return asyncio.sleep(t1 - tt)


def stop():
    
    from .device import stop_sound
    stop_sound()

